package main

import (
	"errors"
	"flag"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/BurntSushi/toml"
)

// 配置文件结构
type Config struct {
	Cache struct {
		CacheDir string `toml:"cache_dir"`
		TmpDir   string `toml:"tmp_dir"`
		MaxSize  int64  `toml:"max_size"`
		MinSize  int64  `toml:"min_size"`
		Expiry   string `toml:"expiry"`
	} `toml:"cache"`

	Proxy []struct {
		Path   string `toml:"path"`
		Target string `toml:"target"`
	} `toml:"proxy"`
}

var (
	config Config

	cacheLoaders   = make(map[string]*CacheLoader)
	cacheLoadersMu sync.Mutex
)

type CacheLoader struct {
	cond       *sync.Cond
	downloaded int64
	err        error
	done       bool
}

func init() {
}

func main() {
	// 解析命令行参数
	configPath := flag.String("c", "config.toml", "Path to the configuration file")
	flag.Parse()

	// 加载配置文件
	if _, err := toml.DecodeFile(*configPath, &config); err != nil {
		log.Fatalf("Failed to load config: %v", err)
	}

	// 初始化缓存目录
	if err := os.MkdirAll(config.Cache.CacheDir, 0755); err != nil {
		log.Fatalf("Failed to create cache directory: %v", err)
	}

	// 初始化临时缓存目录
	if err := os.MkdirAll(config.Cache.TmpDir, 0755); err != nil {
		log.Fatalf("Failed to create tmp cache directory: %v", err)
	}

	// 启动服务
	http.HandleFunc("/", proxyHandler)
	go cleanupCache()
	log.Printf("Starting server on :8080")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

// GetProxyTarget 获取代理目标URL
func GetProxyTarget(path string) (string, bool) {
	longestPrefix := ""
	var target string

	// 寻找最长匹配前缀
	for _, proxy := range config.Proxy {
		if strings.HasPrefix(path, proxy.Path) && len(proxy.Path) > len(longestPrefix) {
			longestPrefix = proxy.Path
			target = proxy.Target
		}
	}

	if longestPrefix != "" {
		// 处理路径拼接，确保只有一个斜杠
		trimmedPath := strings.TrimPrefix(path, longestPrefix)
		if !strings.HasPrefix(trimmedPath, "/") && !strings.HasSuffix(target, "/") {
			target += "/"
		}
		return target + trimmedPath, true
	}
	return "", false
}

func proxyHandler(w http.ResponseWriter, r *http.Request) {
	path := r.URL.Path
	userAgent := r.UserAgent()

	// 使用请求路径作为缓存键
	cacheKey := path
	cacheFile := filepath.Join(config.Cache.CacheDir, pathToFilename(cacheKey))
	tmpCacheFile := filepath.Join(config.Cache.TmpDir, pathToFilename(cacheKey))

	// 检查缓存有效性
	if info, err := os.Stat(cacheFile); err == nil {
		expiryDuration, _ := time.ParseDuration(config.Cache.Expiry)
		if time.Since(info.ModTime()) < expiryDuration {
			file, err := os.Open(cacheFile)
			if err != nil {
				http.Error(w, "Failed to open cache file", http.StatusInternalServerError)
				return
			}
			serveFile(w, file, nil)
			return
		}
		os.Remove(cacheFile) // 删除过期缓存
	}

	// 处理并发请求
	cacheLoadersMu.Lock()
	// 避免downloader move cache
	if _, err := os.Stat(cacheFile); err == nil {
		file, err := os.Open(cacheFile)
		cacheLoadersMu.Unlock()
		if err != nil {
			http.Error(w, "Failed to open cache file", http.StatusInternalServerError)
			return
		}
		serveFile(w, file, nil)
	} else {
		loader, exists := cacheLoaders[cacheKey]
		if !exists {
			file, err := os.OpenFile(tmpCacheFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
			if err != nil {
				cacheLoadersMu.Unlock()
				http.Error(w, "Failed to create temp file", http.StatusInternalServerError)
				return
			}

			loader = &CacheLoader{
				cond: sync.NewCond(&sync.Mutex{}),
			}
			cacheLoaders[cacheKey] = loader
			go fetchAndCache(path, userAgent, tmpCacheFile, cacheFile, file, loader)
		}
		file, err := os.Open(tmpCacheFile)
		cacheLoadersMu.Unlock()
		if err != nil {
			http.Error(w, "Failed to open tmp cache file", http.StatusInternalServerError)
			return
		}
		serveFile(w, file, loader)
	}
}

// downloader
func fetchAndCache(path, userAgent, tempFile, cacheFile string, file *os.File, loader *CacheLoader) {
	defer func() {
		file.Close()
		loader.cond.L.Lock()
		loader.done = true
		loader.cond.L.Unlock()
		loader.cond.Broadcast()

		cacheLoadersMu.Lock()
		delete(cacheLoaders, path)
		cacheLoadersMu.Unlock()
	}()

	// 获取实际目标URL
	targetURL, found := GetProxyTarget(path)
	if !found {
		loader.err = errors.New("No Matching proxy rule")
		return
	}

	// 实现断点续传逻辑
	fileInfo, _ := file.Stat()
	start := fileInfo.Size()

	req, _ := http.NewRequest("GET", targetURL, nil)
	req.Header.Set("User-Agent", userAgent)
	if start > 0 {
		req.Header.Set("Range", fmt.Sprintf("bytes=%d-", start))
	}

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		loader.err = err
		return
	}
	defer resp.Body.Close()

	// 处理206（部分内容）和200（全部内容）状态码
	if resp.StatusCode == http.StatusPartialContent && start > 0 {
		// 继续追加内容
	} else {
		// 重置文件指针
		file.Truncate(0)
		start = 0
	}

	// 流式写入
	buf := make([]byte, 1024*1024) // 1MB buffer
	for {
		n, err := resp.Body.Read(buf)
		if n > 0 {
			if _, werr := file.Write(buf[:n]); werr != nil {
				loader.cond.L.Lock()
				loader.err = werr
				loader.cond.L.Unlock()
				loader.cond.Broadcast()
				return
			}
			file.Sync()
			loader.cond.L.Lock()
			loader.downloaded += int64(n)
			loader.cond.L.Unlock()
			loader.cond.Broadcast()
		}
		if err != nil {
			if err != io.EOF {
				loader.err = err
			}
			break
		}
	}

	// 移动临时文件到缓存目录
	if loader.err == nil {
		os.Rename(tempFile, cacheFile)
	}
}

func serveFile(w http.ResponseWriter, file *os.File, loader *CacheLoader) {
	defer file.Close()

	if _, err := file.Seek(0, io.SeekStart); err != nil {
		http.Error(w, "failed to reset cache file", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/octet-stream")
	if loader == nil {
		io.Copy(w, file)
		return
	}

	currentPos := int64(0)
	for {
		loader.cond.L.Lock()
		downloaded := loader.downloaded
		done := loader.done
		err := loader.err
		loader.cond.L.Unlock()

		if currentPos >= downloaded && !done {
			loader.cond.L.Lock()
			loader.cond.Wait()
			loader.cond.L.Unlock()
			continue
		}

		if currentPos < downloaded {

			n, err := io.CopyN(w, file, downloaded-currentPos)
			currentPos += n

			if err != nil {
				return
			}
			continue
		}

		if done {
			if err != nil {
				http.Error(w, loader.err.Error(), http.StatusInternalServerError)
			}
			return
		}
	}
}

// pathToFilename 将请求路径转换为安全文件名
func pathToFilename(path string) string {
	// 替换特殊字符
	clean := strings.ReplaceAll(path, "/", "_")
	clean = regexp.MustCompile(`[^a-zA-Z0-9_-]`).ReplaceAllString(clean, "")
	if len(clean) > 255 {
		clean = clean[:255]
	}
	return clean
}

func cleanupCache() {
	expiryDuration, _ := time.ParseDuration(config.Cache.Expiry)
	for range time.Tick(5 * time.Minute) {
		filepath.Walk(config.Cache.CacheDir, func(path string, info os.FileInfo, err error) error {
			if err != nil || info.IsDir() {
				return nil
			}

			if time.Since(info.ModTime()) > expiryDuration {
				os.Remove(path)
			}
			return nil
		})
	}
}
